package com.hospital.billcommon.module.business;

import com.alibaba.csp.sentinel.util.StringUtil;
import com.hospital.api.RemoteDictionaryService;
import com.hospital.billcommon.module.controller.dto.*;
import com.hospital.billcommon.module.entity.QueryBillItemPrice;
import com.hospital.billcommon.module.filter.CutOffFactory;
import com.hospital.billcommon.module.filter.FilterChain;
import com.hospital.billcommon.module.mapper.QueryBillItemPriceMapper;
import com.hospital.common.core.module.entity.ChargePriceSchedule;
import com.hospital.common.core.module.entity.ChargeSpecialExceptDict;
import com.hospital.common.core.module.entity.ChargeSpecialItemDict;
import com.hospital.common.core.module.entity.ChargeTypeDict;
import com.hospital.common.core.module.entity.query.BillItem;
import com.hospital.common.core.utils.DateUtil;
import com.hospital.common.core.utils.RedisUtils;
import com.hospital.common.core.utils.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestTemplate;

import java.sql.SQLException;
import java.text.ParseException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @description 扣费业务
 * @author: liuwei
 * @create 2020/5/15 9:04 上午
 **/
@Slf4j
abstract class AbstractChargingBusiness<T extends BillItem> implements ChargeBusinessInfo {

    private QueryBillItemPriceMapper queryBillItemPriceMapper = SpringContextHolder.getBean(QueryBillItemPriceMapper.class);

    private RemoteDictionaryService remoteDictionaryService = SpringContextHolder.getBean(RemoteDictionaryService.class);

    /**
     * 整体扣费业务
     *
     * @param billDto
     * @return
     */
    @Override
    public boolean chargingFee(BillDto billDto, RestTemplate restTemplate) throws Exception {
//        List<T> billItemList = unpackItem(billDto);
//        setBillItemCurrentSingleCosts(billItemList, billDto.getChargeType());
//        billItemList = billItemList.stream().filter(c -> c.getCosts() > 0).collect(Collectors.toList());
//        ChargePriceSchedule chargePriceSchedule = isCalculateFee(billDto.getChargeType());
//        double cutoffVal = 1.0d;
//        if (chargePriceSchedule != null) {
//            calculateFee(billItemList, billDto.getChargeType(), billDto.getOutpOrinp());
//            cutoffVal = (double) chargePriceSchedule.getPriceCoeffNumerator() / chargePriceSchedule.getPriceCoeffDenominator();
//        }
//        int itemNo = 1;
//        for (T billItem : billItemList) {
//            if (!billItem.isCount() && billItem.getCosts() > 0) {
//                billItem.setCharges(Math.round(billItem.getCosts() * billItem.getAmount() * cutoffVal * 100) * 0.01d);
//                billItem.setCosts(Math.round(billItem.getCosts() * billItem.getAmount() * 100) * 0.01d);
//                billItem.setCutoffVal(cutoffVal);
//                billItem.setCount(true);
//            }
//            billItem.setItemNo(String.valueOf(itemNo));
//            itemNo++;
//
//        }
        List<T> billItemList = getbillItemList(billDto, true);
        //此处判断charges(应收)
        double sum = billItemList.stream().mapToDouble(BillItem::getCharges).sum();
        sum = Double.parseDouble(String.format("%.2f", sum));
        String patient_key = "patient_id_%s";
        try {
            if (RedisUtils.tryGetDistributedLock(String.format(patient_key, billDto.getPatientId()), "1", 10000)) {
                String visitId = billDto.getVisitId() == null ? "" : billDto.getVisitId();
                String createChannel = null;
                try {
                    billDto.getSettlePatientInfo().get(0).setOrderAmt(String.valueOf(sum));
                    createChannel = billDto.getSettlePatientInfo().get(0).getCreateChannel();
                } catch (NullPointerException e) {

                }
                boolean isPay = calculateCurrentRemain(sum, billDto.getPatientId(), visitId, billDto.getChargeType(), createChannel);
                try {
                    if (isPay) {
                        return updatePatientData(billItemList, billDto, restTemplate);
                    } else {
                        throw new RuntimeException("用户余额不足扣除当前费用，请患者充值后再计费");
                    }
                } catch (SQLException sql) {
                    throw new RuntimeException("数据库异常:" + new String(sql.getMessage().getBytes("ISO-8859-1"),"UTF-8"));
                }

            } else {
//                log.info("当前用户正在扣除其它费用，请稍候计费");
                throw new RuntimeException("当前用户正在扣除其它费用，请稍候计费");
            }

        } catch (Exception e) {
//            log.info(e.getMessage());
            throw new RuntimeException(e.getMessage());
        } finally {
            RedisUtils.releaseDistributedLock(String.format(patient_key, billDto.getPatientId()), "1");
        }
    }

    /**
     * 退费流程控制
     *
     * @param refundBillVo
     * @return
     * @throws Exception
     */
    @Override
    public boolean chargingRefundFee(RefundBillVo refundBillVo, RestTemplate restTemplate) throws Exception {
        List<RefundBillDetailVo> refundBillDetailList = refundBillVo.getRefundBillDetailList();
        List<T> refundData = getRefundData(refundBillDetailList, refundBillVo);
        boolean b = insertRefundPatientData(refundData, refundBillVo, restTemplate);
        return b;
    }

    /**
     * 准备插入数据库进行退费操作的数据
     *
     * @param
     * @return
     */
    protected abstract List<T> getRefundData(List<RefundBillDetailVo> refundBillDetailList, RefundBillVo refundBillVo) throws Exception;

    /**
     * 插入数据库进行退费操作
     *
     * @param billItemList
     * @return
     */
    protected abstract boolean insertRefundPatientData(List<T> billItemList, RefundBillVo billDto, RestTemplate restTemplate) throws Exception;


    @Override
    public BillDto queryFee(BillDto billDto) throws Exception {
        List<T> billItemList = getbillItemList(billDto, false);
        List<ClinicType> clinicTypeList = billDto.getClinicTypeList();
        for (ClinicType clinicType : clinicTypeList) {
            List<ClinicItem> clinicItemList = clinicType.getClinicItemList();
            for (ClinicItem clinicItem : clinicItemList) {
                List<BillItem> billItemList1 = billItemList.stream().filter(s -> s.getClinicItemCode().equals(clinicItem.getClinicItemCode())).collect(Collectors.toList());
                clinicItem.setBillItemList(billItemList1);
//                DecimalFormat df = new DecimalFormat("#.##");
                double charges = Math.round(billItemList1.stream().mapToDouble(BillItem::getCharges).sum() * 100) * 0.01d;
                double costs = Math.round(billItemList1.stream().mapToDouble(BillItem::getCosts).sum() * 100) * 0.01d;
//                double charges = Double.parseDouble(df.format(billItemList1.stream().mapToDouble(BillItem::getCharges).sum()));
//                double costs = Double.parseDouble(df.format(billItemList1.stream().mapToDouble(BillItem::getCosts).sum()));
                clinicItem.setCharges(charges);
                clinicItem.setCosts(costs);
            }
            clinicType.setClinicItemList(clinicItemList);
        }
        return billDto;
    }

    /**
     * 获取打折后项目明细费用
     * billdto 入参
     * type 操作类型  false 查询  true 计费
     *
     * @param billDto
     */
    private List<T> getbillItemList(BillDto billDto, boolean type) throws Exception {
        List<T> billItemList = unpackItem(billDto);
        if (billItemList.size() == 0) {
            throw new Exception("根据开单项目不能获取计费项目！");
        }
        String visitdate = "'" + DateUtil.getTime() + "'" ;
        if (StringUtil.isNotBlank(billDto.getClinicTypeList().get(0).getClinicItemList().get(0).getPrimaryDate())){
            visitdate = "'" + billDto.getClinicTypeList().get(0).getClinicItemList().get(0).getPrimaryDate() + "'";
        }
        setBillItemCurrentSingleCosts(billItemList, billDto.getChargeType(),visitdate);
//        billItemList = billItemList.stream().filter(c -> c.getCosts() > 0).collect(Collectors.toList());
        ChargePriceSchedule chargePriceSchedule = isCalculateFee(billDto.getChargeType());
        double cutoffVal = 1.0d;
        if (chargePriceSchedule != null) {
            calculateFee(billItemList, billDto.getChargeType(), billDto.getOutpOrinp());
            cutoffVal = (double) chargePriceSchedule.getPriceCoeffNumerator() / chargePriceSchedule.getPriceCoeffDenominator();
        }
        int beforeNo = 1;
        if (type) {
            try {
                if (billDto.getOutpOrinp().equals("02")) {
                    int inpMaxItemNo = queryBillItemPriceMapper.getInpMaxItemNo(billDto.getPatientId(), billDto.getVisitId());
                    beforeNo = (inpMaxItemNo == 0) ? 1 : inpMaxItemNo + 1;
                } else {

                    int outpItmeNo = queryBillItemPriceMapper.getOutpItmeNo(DateUtil.getTime(billDto.getVisitDate()), billDto.getVisitNo());
                    beforeNo = outpItmeNo == 0 ? 1 : outpItmeNo + 1;
                }
            } catch (NullPointerException e) {
                beforeNo = 1;
            }
        }
        for (T billItem : billItemList) {
            if (!billItem.isCount() && billItem.getCosts() > 0) {
                double charges = Math.round(billItem.getCosts() * billItem.getAmount() * cutoffVal * 1000) * 0.001d;
                double costs = Math.round(billItem.getCosts() * billItem.getAmount() * 1000) * 0.001d;
                double retailPrice = Math.round(billItem.getCurrentPrice() * cutoffVal * 1000) * 0.001d;
                charges = Math.round(charges * 100) * 0.01d;
                costs = Math.round(costs * 100) * 0.01d;
                double roundAmt = Math.round((charges - billItem.getCurrentPrice() * cutoffVal * billItem.getAmount()) * 1000) * 0.001d;
                billItem.setCharges(charges);
                billItem.setCosts(costs);
                billItem.setRetailPrice(Math.round(retailPrice * 1000) * 0.001d);
                billItem.setRoundAmt(roundAmt);
                billItem.setCount(true);
            }
            billItem.setItemNo(String.valueOf(beforeNo));
            beforeNo++;
        }
        return billItemList;
    }

    /**
     * 添加费别对应的收费项目单价
     *
     * @param billItemList
     * @param chargeType
     */
    private void setBillItemCurrentSingleCosts(List<T> billItemList, String chargeType,String visitDate) {
        List<String> itemCodeList = billItemList.stream().map(BillItem::getItemCode).distinct().collect(Collectors.toList());
        List<String> itemClassList = billItemList.stream().map(BillItem::getItemClass).distinct().collect(Collectors.toList());
        List<String> itemSpecList = billItemList.stream().map(BillItem::getItemSpec).distinct().collect(Collectors.toList());
        List<QueryBillItemPrice> queryBillItemPrices = null;
        ChargeTypeDict chargeTypeIndicator = remoteDictionaryService.getchargeTypeDictList().stream().filter(c -> c.getChargeTypeName().equals(chargeType)).findFirst().orElse(null);
        if (chargeTypeIndicator != null) {
            queryBillItemPrices = queryBillItemPriceMapper.getAllNeedPrice(chargeTypeIndicator.getChargePriceIndicator() == 0 ?
                    "PRICE" : chargeTypeIndicator.getChargePriceIndicator() == 1 ?
                    "PREFER_PRICE" : "FOREIGNER_PRICE", itemCodeList, itemSpecList, itemClassList,visitDate);

            for (T item : billItemList) {
                if (item.getItemClass() != null && item.getItemSpec() != null && item.getClinicItemCode() != null) {
                    QueryBillItemPrice first = queryBillItemPrices.stream().filter(c -> item.getItemClass().equals(c.getItemClass()) && item.getItemSpec().equals(c.getItemSpec()) && item.getItemCode().equals(c.getItemCode())).findFirst().orElse(null);
                    if (first != null) {
                        item.setCosts(first.getPrice());
//                    setClassOnRcpt(item, first.getClassOnInpRcpt());
                        item.setItemName(first.getItemName());
                        item.setUnits(first.getUnits());
                        item.setCurrentPrice(first.getCurrentPrice());
                        item.setChargePriceIndicator(chargeTypeIndicator.getChargePriceIndicator());
                    }
                }
            }
        }

    }

    protected abstract void setClassOnRcpt(T item, String classOnInpRcpt);

    /**
     * 插入数据库进行扣费操作
     *
     * @param billItemList
     * @return
     */
    protected abstract boolean updatePatientData(List<T> billItemList, BillDto billDto, RestTemplate restTemplate) throws Exception;

    /**
     * 计算当前余额是否可以进行扣费操作
     *
     * @param sum
     * @param patientId
     * @return
     */
    protected abstract boolean calculateCurrentRemain(double sum, String patientId, String visitId, String chargeType, String createChannel);

    /**
     * 查找当前费别收费系数
     *
     * @param chargeType
     * @return
     */
    protected com.hospital.common.core.module.entity.ChargePriceSchedule isCalculateFee(String chargeType) {
        List<com.hospital.common.core.module.entity.ChargePriceSchedule> chargePriceScheduleList = remoteDictionaryService.getChargePriceScheduleList();
        com.hospital.common.core.module.entity.ChargePriceSchedule chargePriceSchedule = chargePriceScheduleList.stream().filter(c -> c.getChargeType().equals(chargeType)).findFirst().orElse(null);
        return chargePriceSchedule;
    }

    /**
     * 打折操作 进入过滤器链来过滤对应费用
     *
     * @param billItemList
     */
    private void calculateFee(List<T> billItemList, String chargeType, String outP) {
        FilterChain filterChain = new FilterChain();
        filterChain.addFilters(CutOffFactory.getCutOffList("com.hospital.billcommon.module.filter"));
        List<ChargeSpecialItemDict> chargeSpecialItemDicts = remoteDictionaryService.getchargeSpecialItemDictList();
        List<ChargeSpecialExceptDict> chargeSpecialExceptDicts = remoteDictionaryService.getchargeSpecialExceptDictList();
        filterChain.doFilter(billItemList, chargeType, outP,chargeSpecialItemDicts,chargeSpecialExceptDicts);
    }

    /**
     * 拆包操作 提取用户的单项费用
     *
     * @param billDto
     * @return
     */
    protected abstract List<T> unpackItem(BillDto billDto) throws ParseException;
}
